13.3 Logistische Regression mit Scikit-Learn#
Lernziele#
Lernziele
Sie können ein logistisches Regressionsmodell mit Scikit-Learn trainieren.
LogisticRegression#
Scikit-Learn bietet ein logistisches Regressionsmodell an, bei dem verschiedene Gradientenverfahren im Hintergrund die Gewichte bestimmen, die zu einer minimalen mittleren Kostenfunktion führen. Die Dokumentation zu dem logistischen Regressionsmodell findet sich hier: scikit-learn.org → LogisticRegression.
Wir wenden nun das Scikit-Learn-Modell auf unser Beispiel der binären Klassifikation “Ligazugehörigkeit abhängig vom Marktwert” deutscher Fußballvereine an. Dazu wiederholen laden wir die Daten und filtern zunächst nach Vereinen der 2. Bundesliga oder der 3. Liga.
# import all data
import pandas as pd
data_raw = pd.read_csv('data/20220801_Marktwert_Bundesliga.csv', skiprows=5, header=0, index_col=0)
# filter wrt 2. Bundesliga and 3. Liga
data = data_raw[ data_raw['Ligazugehörigkeit'] != 'Bundesliga' ]
# print all data samples
data.head(38)
| Ligazugehörigkeit | Wert | Kadergröße | |
|---|---|---|---|
| Verein | |||
| Hamburger SV | 2. Bundesliga | 34.85 | 28 |
| Arminia Bielefeld | 2. Bundesliga | 31.10 | 26 |
| SpVgg Greuther Fürth | 2. Bundesliga | 24.55 | 27 |
| FC St. Pauli | 2. Bundesliga | 23.50 | 28 |
| Fortuna Düsseldorf | 2. Bundesliga | 19.55 | 23 |
| Hannover 96 | 2. Bundesliga | 22.60 | 28 |
| 1.FC Nürnberg | 2. Bundesliga | 23.25 | 27 |
| SV Darmstadt 98 | 2. Bundesliga | 18.93 | 26 |
| Karlsruher SC | 2. Bundesliga | 16.95 | 30 |
| 1.FC Heidenheim 1846 | 2. Bundesliga | 16.65 | 27 |
| Holstein Kiel | 2. Bundesliga | 16.38 | 30 |
| SC Paderborn 07 | 2. Bundesliga | 16.38 | 27 |
| SV Sandhausen | 2. Bundesliga | 13.15 | 25 |
| SSV Jahn Regensburg | 2. Bundesliga | 12.10 | 25 |
| FC Hansa Rostock | 2. Bundesliga | 13.00 | 28 |
| 1.FC Kaiserslautern | 2. Bundesliga | 9.45 | 28 |
| 1.FC Magdeburg | 2. Bundesliga | 11.10 | 31 |
| Eintracht Braunschweig | 2. Bundesliga | 8.83 | 26 |
| Borussia Dortmund II | 3. Liga | 13.05 | 31 |
| SG Dynamo Dresden | 3. Liga | 9.18 | 31 |
| FC Ingolstadt 04 | 3. Liga | 8.43 | 27 |
| SC Freiburg II | 3. Liga | 6.23 | 31 |
| FC Erzgebirge Aue | 3. Liga | 7.53 | 30 |
| TSV 1860 München | 3. Liga | 6.65 | 30 |
| SV Waldhof Mannheim | 3. Liga | 5.73 | 23 |
| MSV Duisburg | 3. Liga | 5.55 | 28 |
| FC Viktoria Köln | 3. Liga | 6.60 | 32 |
| 1. FC Saarbrücken | 3. Liga | 6.15 | 26 |
| VfL Osnabrück | 3. Liga | 5.60 | 27 |
| SV Wehen Wiesbaden | 3. Liga | 5.53 | 25 |
| Rot-Weiss Essen | 3. Liga | 4.68 | 29 |
| SV 07 Elversberg | 3. Liga | 4.05 | 26 |
| SC Verl | 3. Liga | 3.68 | 28 |
| Hallescher FC | 3. Liga | 3.95 | 24 |
| SV Meppen | 3. Liga | 3.93 | 26 |
| FSV Zwickau | 3. Liga | 3.80 | 24 |
| SpVgg Bayreuth | 3. Liga | 3.25 | 30 |
| VfB Oldenburg | 3. Liga | 3.06 | 27 |
Als nächstes formulieren wir das Klassifikationsproblem: Gegeben ist ein Verein mit seinem Marktwert. Spielt der Verein in der 2. Bundesliga?
Die Klasse 2. Bundesliga wird in den Daten als 1 codiert, da der ML-Algorithmus nur mit numerischen Daten arbeiten kann. Den String 3. Liga ersetzen wir in den Trainingsdaten durch eine 0.
# encode categorical data
data.replace('2. Bundesliga', 1, inplace=True)
data.replace('3. Liga', 0, inplace=True)
/var/folders/hd/xdjzhymn4x393gh7v6ws4v880000gn/T/ipykernel_6173/2351413053.py:2: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
data.replace('2. Bundesliga', 1, inplace=True)
/var/folders/hd/xdjzhymn4x393gh7v6ws4v880000gn/T/ipykernel_6173/2351413053.py:3: FutureWarning: Downcasting behavior in `replace` is deprecated and will be removed in a future version. To retain the old behavior, explicitly call `result.infer_objects(copy=False)`. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
data.replace('3. Liga', 0, inplace=True)
/var/folders/hd/xdjzhymn4x393gh7v6ws4v880000gn/T/ipykernel_6173/2351413053.py:3: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
data.replace('3. Liga', 0, inplace=True)
Jetzt können wir das logistische Regressionsmodell laden:
from sklearn.linear_model import LogisticRegression
logistic_regression = LogisticRegression()
Die Daten werden jetzt in Matrizen gepackt und in Trainings- und Testdaten unterteilt:
import numpy as np
from sklearn.model_selection import train_test_split
X = data[['Wert']]
y = data['Ligazugehörigkeit']
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
Danach können wir das logistische Regressionsmodell trainieren:
logistic_regression.fit(X_train, y_train)
LogisticRegression()In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression()
Und dann als nächstes beurteilen, wie viele der Testdaten korrekt klassfiziert werden.
logistic_regression.score(X_test, y_test)
0.9
90 % der Testdaten werden korrekt klassifiziert. Mit einer anderen Aufteilung in
Trainings- und Testdaten können wir auch höhere Erkennungsraten erzielen.
Beispielsweise führt ein Split mit random_state=1 zu einer 100 % genauen
Klassifikation der Testdaten.
Als nächstes lassen wir Python alle Daten zusammen mit der Wahrscheinlichkeitsfunktion visualisieren.
# extrahiere die Gewichte des logistischen Regressionsmodells
gewichte = np.concatenate((logistic_regression.intercept_, logistic_regression.coef_[:,0]))
print(f'Gewichte: {gewichte}')
# definiere Wahrschinelichkeitsfunktion
def wahrscheinlichkeitsfunktion(x, w):
z = w[0] + x * w[1]
return 1/(1+np.exp(-z))
# stelle Wartetabelle der Wahrscheinlichkeitsfunktion auf
x = np.linspace(0, 35)
sigma_z = wahrscheinlichkeitsfunktion(x, gewichte)
# trenne Daten gemäß Ligazugehörigkeit
data_zweite_liga = data[data['Ligazugehörigkeit'] == 1]
data_dritte_liga = data[data['Ligazugehörigkeit'] == 0]
Gewichte: [-6.73251103 0.63014533]
import plotly.express as px
import plotly.graph_objects as go
fig3 = px.scatter(data_dritte_liga, x = 'Wert', y = 'Ligazugehörigkeit')
fig2 = px.scatter(data_zweite_liga, x = 'Wert', y = 'Ligazugehörigkeit')
fig_model = px.line(x = x, y = sigma_z)
fig = go.Figure(fig_model.data + fig2.data + fig3.data)
fig.update_layout(title='Klassifikation 2. Bundesliga / 3. Liga',
xaxis_title='Marktwert',
yaxis_title='Ligazugehörigkeit')
fig.show()
Aus der Visualisierung der Wahrscheinlichkeitsfunktion können wir grob
abschätzen, bei welchem Marktwert ein Verein als Zweit- oder Drittligist
klassifiziert wird. Die Wahrscheinlichkeitsfunktion schneidet die 50 %
Grenzlinie ungefähr bei einem Marktwert von 11 Mio. Euro. Etwas genauer können
wir diese Grenze durch das Kommando fsolve aus dem Scipy-Modul bestimmen
lassen:
from scipy.optimize import fsolve
x_grenze = fsolve(lambda x: wahrscheinlichkeitsfunktion(x, gewichte) - 0.5, 11.0)
print('Grenze des Marktwertes: {:.2f} Mio. Euro'.format(x_grenze[0]))
Grenze des Marktwertes: 10.68 Mio. Euro
Zusammenfassung#
In diesem Abschnitt haben wir an einem Beispiel gesehen, wie das logistische Regressionsmodell von Scikit-Learn trainiert und bewertet wird.